
#include "notification_service.h"

#include "base/lazy_instance.h"
#include "base/threading/thread_local.h"

#include "notification_observer.h"
#include "notification_types.h"

static base::LazyInstance<base::ThreadLocalPointer<NotificationService> >
lazy_tls_ptr(base::LINKER_INITIALIZED);

// static
NotificationService* NotificationService::current()
{
    return lazy_tls_ptr.Pointer()->Get();
}

// static
bool NotificationService::HasKey(const NotificationSourceMap& map,
                                 const NotificationSource& source)
{
    return map.find(source.map_key()) != map.end();
}

NotificationService::NotificationService()
{
    DCHECK(current() == NULL);
    lazy_tls_ptr.Pointer()->Set(this);
}

void NotificationService::AddObserver(NotificationObserver* observer,
                                      int type,
                                      const NotificationSource& source)
{
    // We have gotten some crashes where the observer pointer is NULL. The problem
    // is that this happens when we actually execute a notification, so have no
    // way of knowing who the bad observer was. We want to know when this happens
    // in release mode so we know what code to blame the crash on (since this is
    // guaranteed to crash later).
    CHECK(observer);

    NotificationObserverList* observer_list;
    if(HasKey(observers_[type], source))
    {
        observer_list = observers_[type][source.map_key()];
    }
    else
    {
        observer_list = new NotificationObserverList;
        observers_[type][source.map_key()] = observer_list;
    }

    observer_list->AddObserver(observer);
#ifndef NDEBUG
    ++observer_counts_[type];
#endif
}

void NotificationService::RemoveObserver(NotificationObserver* observer,
                                         int type,
                                         const NotificationSource& source)
{
    // This is a very serious bug.  An object is most likely being deleted on
    // the wrong thread, and as a result another thread's NotificationService
    // has its deleted pointer in its map.  A garbge object will be called in the
    // future.
    // NOTE: when this check shows crashes, use BrowserThread::DeleteOnIOThread or
    // other variants as the trait on the object.
    CHECK(HasKey(observers_[type], source));

    NotificationObserverList* observer_list =
        observers_[type][source.map_key()];
    if(observer_list)
    {
        observer_list->RemoveObserver(observer);
#ifndef NDEBUG
        --observer_counts_[type];
#endif
    }

    // TODO(jhughes): Remove observer list from map if empty?
}

void NotificationService::Notify(int type,
                                 const NotificationSource& source,
                                 const NotificationDetails& details)
{
    DCHECK(type > notification::NOTIFICATION_ALL) <<
        "Allowed for observing, but not posting.";

    // There's no particular reason for the order in which the different
    // classes of observers get notified here.

    // Notify observers of all types and all sources
    if(HasKey(observers_[notification::NOTIFICATION_ALL], AllSources()) &&
        source!=AllSources())
    {
        FOR_EACH_OBSERVER(NotificationObserver,
            *observers_[notification::NOTIFICATION_ALL][AllSources().map_key()],
            Observe(type, source, details));
    }

    // Notify observers of all types and the given source
    if(HasKey(observers_[notification::NOTIFICATION_ALL], source))
    {
        FOR_EACH_OBSERVER(NotificationObserver,
            *observers_[notification::NOTIFICATION_ALL][source.map_key()],
            Observe(type, source, details));
    }

    // Notify observers of the given type and all sources
    if(HasKey(observers_[type], AllSources()) && source!=AllSources())
    {
        FOR_EACH_OBSERVER(NotificationObserver,
            *observers_[type][AllSources().map_key()],
            Observe(type, source, details));
    }

    // Notify observers of the given type and the given source
    if(HasKey(observers_[type], source))
    {
        FOR_EACH_OBSERVER(NotificationObserver,
            *observers_[type][source.map_key()],
            Observe(type, source, details));
    }
}


NotificationService::~NotificationService()
{
    lazy_tls_ptr.Pointer()->Set(NULL);

#ifndef NDEBUG
    for(int i=0; i<static_cast<int>(observer_counts_.size()); i++)
    {
        if(observer_counts_[i] > 0)
        {
            // This may not be completely fixable -- see
            // http://code.google.com/p/chromium/issues/detail?id=11010 .
            VLOG(1) << observer_counts_[i] << " notification observer(s) leaked "
                "of notification type " << i;
        }
    }
#endif

    for(int i=0; i<static_cast<int>(observers_.size()); i++)
    {
        NotificationSourceMap omap = observers_[i];
        for(NotificationSourceMap::iterator it=omap.begin();
            it!=omap.end(); ++it)
        {
            delete it->second;
        }
    }
}

NotificationObserver::NotificationObserver() {}

NotificationObserver::~NotificationObserver() {}